﻿//"The contents of this file are subject to the Mozilla Public License
//Version 1.1 (the "License"); you may not use this file except in
//compliance with the License. You may obtain a copy of the License at
//http://www.mozilla.org/MPL/

//Software distributed under the License is distributed on an "AS IS"
//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//License for the specific language governing rights and limitations
//under the License.

//The Original Code is TSharp.

//The Initial Developer of the Original Code is Mar3ek (http://mar3ek.wordpress.com).
//Portions created by Mar3ek are Copyright (C) 2010
//Mar3ek (http://mar3ek.wordpress.com). All Rights Reserved.

//Contributor(s): ______________________________________.

//Alternatively, the contents of this file may be used under the terms
//of the _____ license (the  "[___] License"), in which case the
//provisions of [______] License are applicable instead of those
//above.  If you wish to allow use of your version of this file only
//under the terms of the [____] License and not to allow others to use
//your version of this file under the MPL, indicate your decision by
//deleting  the provisions above and replace  them with the notice and
//other provisions required by the [___] License.  If you do not delete
//the provisions above, a recipient may use your version of this file
//under either the MPL or the [___] License."

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml.Linq;

namespace TSharp
{
	public sealed partial class Twitter
	{
		#region Accounts

		/// <summary>
		/// Tests validity of supplied user credentials.
		/// </summary>
		/// <returns>True if the credentials are valid, otherwise false</returns>
		public bool ValidateCredentials()
		{
			try
			{
				SendRequest(RequestMethod.Get, "https://api.twitter.com/1/account/verify_credentials.xml", true);
				return true;
			}
			catch (TwitterException)
			{
				return false;
			}
		}

		/// <summary>
		/// Returns the remaining number of API requests available to the requesting user before the API limit is reached for the current hour.
		/// </summary>
		/// <returns>Object describing rate limits</returns>
		public TwitterRateLimitDescriptor GetRateLimitStatus()
		{
			var result = SendRequest(RequestMethod.Get, "https://api.twitter.com/1/account/rate_limit_status.xml",
										false);
			var doc = XDocument.Parse(result);
			var hashElement = doc.Element("hash");
			TwitterRateLimitDescriptor descriptor = null;
			if (hashElement != null)
			{
				// ReSharper disable PossibleNullReferenceException
				var nextResetString = hashElement.Element("reset-time-in-seconds").Value;
				var nextReset = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(int.Parse(nextResetString));

				descriptor = new TwitterRateLimitDescriptor(
					int.Parse(hashElement.Element("hourly-limit").Value),
					int.Parse(hashElement.Element("remaining-hits").Value),
					nextReset.ToLocalTime());
				// ReSharper restore PossibleNullReferenceException
			}
			return descriptor;
		}

		/// <summary>
		/// Ends the session of the authenticating user, returning a null cookie.
		/// </summary>
		/// <remarks>Use this method to sign users out of client-facing applications like widgets.</remarks>
		public void EndSession()
		{
			SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/end_session.xml", false);
		}

		/// <summary>
		/// Sets which device Twitter delivers updates to for the authenticating user.
		/// </summary>
		/// <param name="device">Specifies the target device</param>
		/// <returns>Edited user entry</returns>
		/// <remarks>Sending <b>none</b> as the <b>device</b> parameter will disable SMS updates.</remarks>
		public TwitterUser UpdateDeliveryDevice(DeliveryDevice device)
		{
			string delDevice;

			switch (device)
			{
				case DeliveryDevice.None:
					delDevice = "none";
					break;
				default:
					delDevice = "sms";
					break;
			}

			var result = SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/update_delivery_device.xml",
									 false, new KeyValuePair<string, string>("device", delDevice));
			var doc = XDocument.Parse(result);

			return ParseAuthor(doc.Element("user"));
		}

		/// <summary>
		/// Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com.
		/// </summary>
		/// <param name="profileBackgroundColor">Profile background color</param>
		/// <param name="profileTextColor">Profile text color</param>
		/// <param name="profileLinkColor">Profile link color</param>
		/// <param name="profileSidebarFillColor">Profile sidebar's background color</param>
		/// <param name="profileSidebarBorderColor">Profile sidebar's border color</param>
		/// <returns>Edited user entry</returns>
		public TwitterUser UpdateProfileColors(Color profileBackgroundColor,
											   Color profileTextColor,
											   Color profileLinkColor,
											   Color profileSidebarFillColor,
											   Color profileSidebarBorderColor)
		{
			var result = SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/update_profile_colors.xml",
									 false, new[]
												{
													new KeyValuePair<string, string>("profile_background_color",
																					 ColorToHexString(profileBackgroundColor)),
													new KeyValuePair<string, string>("profile_text_color",
																					 ColorToHexString(profileTextColor)),
													new KeyValuePair<string, string>("profile_link_color",
																					 ColorToHexString(profileLinkColor)),
													new KeyValuePair<string, string>("profile_sidebar_fill_color",
																					 ColorToHexString(profileSidebarFillColor)),
													new KeyValuePair<string, string>("profile_sidebar_border_color",
																					 ColorToHexString(profileSidebarBorderColor))
												});
			var doc = XDocument.Parse(result);

			return ParseAuthor(doc.Element("user"));
		}

		///<summary>
		/// Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com.
		///</summary>
		///<param name="colors">Colors to update</param>
		///<exception cref="ArgumentNullException"></exception>
		///<returns>Edited user entry</returns>
		public TwitterUser UpdateProfileColor(params KeyValuePair<TwitterAccountColor, Color>[] colors)
		{
			if (colors == null || colors.Length == 0) throw new ArgumentNullException("colors");
			var parameters = new List<KeyValuePair<string, string>>();

			foreach (var c in colors)
			{
				string paramName;
				switch (c.Key)
				{
					case TwitterAccountColor.ProfileBackgroundColor:
						paramName = "profile_background_color";
						break;
					case TwitterAccountColor.ProfileLinkColor:
						paramName = "profile_link_color";
						break;
					case TwitterAccountColor.ProfileSidebarBorderColor:
						paramName = "profile_sidebar_border_color";
						break;
					case TwitterAccountColor.ProfileSidebarFillColor:
						paramName = "profile_sidebar_fill_color";
						break;
					default:
						paramName = "profile_sidebar_border_color";
						break;
				}

				if ((from p in parameters select p.Key).FirstOrDefault() == null)
					parameters.Add(new KeyValuePair<string, string>(paramName, ColorToHexString(c.Value)));
			}

			var result = SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/update_profile_colors.xml",
									 false, parameters.ToArray());
			var doc = XDocument.Parse(result);

			return ParseAuthor(doc.Element("user"));
		}

		private void UploadImageInternal(string apiEndpoint, byte[] image, ProfileImageMIME mime)
		{
			string mimeType;

			switch (mime)
			{
				case ProfileImageMIME.Gif:
					mimeType = "image/gif";
					break;
				case ProfileImageMIME.Jpg:
					mimeType = "image/jpeg";
					break;
				default:
					mimeType = "image/png";
					break;
			}

			var request = (HttpWebRequest)WebRequest.Create(apiEndpoint);

			var boundary = Guid.NewGuid().ToString();
			var oAuthHeader = GenerateOAuthHeader("POST", apiEndpoint);

			request.ContentType = String.Format("multipart/form-data;boundary={0}", boundary);
			request.Method = "POST";
			request.ServicePoint.Expect100Continue = false;
			request.Headers.Add("Authorization", oAuthHeader);

			//' Build Contents for Post
			var header = string.Format("--{0}", boundary);
			var footer = string.Format("--{0}--", boundary);

			var contents = new StringBuilder();

			//' Image
			contents.AppendLine(header);
			contents.AppendLine(string.Format("Content-Disposition:form-data; name=\"image\"; filename=\"{0}\"",
											  "twitterProfilePhoto.jpg"));
			contents.AppendLine(string.Format("Content-Type: {0}", mimeType));
			contents.AppendLine();
			contents.AppendLine(Encoding.GetEncoding("iso-8859-1").GetString(image));

			//' Footer
			contents.AppendLine(footer);

			//' Data that is sent with the post
			var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes(contents.ToString());

			request.ContentLength = bytes.Length;

			using (var requestStream = request.GetRequestStream())
			{
				requestStream.Write(bytes, 0, bytes.Length);
			}

			ReadResponse(request, false);
		}

		/// <summary>
		/// Updates the authenticating user's profile image.
		/// </summary>
		/// <param name="image">The avatar image data for the profile (must be 700 kB or less)</param>
		/// <param name="mime">MIME type of the image</param>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentNullException"></exception>
		/// <remarks> Images with width larger than 500 pixels will be scaled down.</remarks>
		public void UploadProfileImage(byte[] image, ProfileImageMIME mime)
		{
			if (image == null || image.Length == 0) throw new ArgumentNullException("image");
			if (image.LongLength > 700 * 1024) throw new FormatException("Image size must be below 700 kB.");
			UploadImageInternal("https://api.twitter.com/1/account/update_profile_image.xml", image, mime);
		}

		/// <summary>
		/// Updates the authenticating user's profile background image.
		/// </summary>
		/// <param name="image">The background image for the profile (must be 800 kB or less)</param>
		/// <param name="mime">MIME type of the image</param>
		/// <exception cref="FormatException"></exception>
		/// <exception cref="ArgumentNullException"></exception>
		/// <remarks>Images with width larger than 2048 pixels will be forceably scaled down.</remarks>
		public void UploadBackgroundImage(byte[] image, ProfileImageMIME mime)
		{
			if (image == null || image.Length == 0) throw new ArgumentNullException("image");
			if (image.LongLength > 800 * 1024) throw new FormatException("Image size must be below 800 kB.");
			UploadImageInternal("https://api.twitter.com/1/account/update_profile_background_image.xml", image, mime);
		}

		/// <summary>
		/// Sets values that users are able to set under the "Account" tab of their settings page.
		/// </summary>
		/// <param name="fullName">Full name associated with the profile. Maximum of 20 characters. </param>
		/// <param name="url">URL associated with the profile. Will be prepended with "http://" if not present. Maximum of 100 characters. </param>
		/// <param name="location">The city or country describing where the user of the account is located. The contents are not normalized or geocoded in any way. Maximum of 30 characters. </param>
		/// <param name="description">A description of the user owning the account. Maximum of 160 characters. </param>
		/// <exception cref="ArgumentNullException"></exception>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		/// <returns>Edited user entry</returns>
		public TwitterUser UpdateProfile(string fullName, Uri url, string location, string description)
		{
			if (fullName == null) throw new ArgumentNullException("fullName");
			if (url == null) throw new ArgumentNullException("url");
			if (location == null) throw new ArgumentNullException("location");
			if (description == null) throw new ArgumentNullException("description");

			if (fullName.Length > 20) throw new ArgumentOutOfRangeException("fullName");
			if (url.OriginalString.Length > 100) throw new ArgumentOutOfRangeException("url");
			if (location.Length > 30) throw new ArgumentOutOfRangeException("location");
			if (description.Length > 160) throw new ArgumentOutOfRangeException("description");

			var result = SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/update_profile.xml", false,
									 new List<KeyValuePair<string, string>>
										 {
											 new KeyValuePair<string, string>("name", fullName),
											 new KeyValuePair<string, string>("url", url.OriginalString),
											 new KeyValuePair<string, string>("location", location),
											 new KeyValuePair<string, string>("description", description)
										 }.ToArray());
			var doc = XDocument.Parse(result);

			return ParseAuthor(doc.Element("user"));
		}

		///<summary>
		/// Sets values that users are able to set under the "Account" tab of their settings page. Only the parameters specified will be updated.
		///</summary>
		///<param name="attributes">List of attributes to update</param>
		///<exception cref="ArgumentNullException"></exception>
		///<returns>Updated user profile</returns>
		public TwitterUser UpdateProfile(params KeyValuePair<TwitterAccountAttributeType, string>[] attributes)
		{
			if (attributes == null) throw new ArgumentNullException("attributes");
			var parameters = new List<KeyValuePair<string, string>>();

			foreach (var c in attributes)
			{
				string paramName;
				switch (c.Key)
				{
					case TwitterAccountAttributeType.Description:
						paramName = "description";
						break;
					case TwitterAccountAttributeType.Location:
						paramName = "location";
						break;
					case TwitterAccountAttributeType.Name:
						paramName = "name";
						break;
					default:
						paramName = "url";
						break;
				}

				if ((from p in parameters select p.Key).FirstOrDefault() == null)
					parameters.Add(new KeyValuePair<string, string>(paramName, c.Value));
			}

			var result = SendRequest(RequestMethod.Post, "https://api.twitter.com/1/account/update_profile_colors.xml",
									 false, parameters.ToArray());
			var doc = XDocument.Parse(result);

			return ParseAuthor(doc.Element("user"));
		}

		/// <summary>
		/// Returns the current count of friends, followers, updates (statuses) and favorites of the authenticating user. 
		/// </summary>
		/// <returns>TwitterAccountTotals object</returns>
		public TwitterAccountTotals GetAccountTotals()
		{
			var response = SendRequest(RequestMethod.Get, "https://api.twitter.com/1/account/totals.xml", true);
			var result = new TwitterAccountTotals { Favorites = 0, Followers = 0, Friends = 0, Updates = 0 };
			var doc = XDocument.Parse(response);
			var root = doc.Element("hash");
			try
			{
				if (root != null)
				{
					result = new TwitterAccountTotals
					{
// ReSharper disable PossibleNullReferenceException
						Favorites = int.Parse(root.Element("followers").Value),
						Followers = int.Parse(root.Element("friends").Value),
						Friends = int.Parse(root.Element("friends").Value),
						Updates = int.Parse(root.Element("updates").Value)
// ReSharper restore PossibleNullReferenceException
					};
				}
			}
			catch (NullReferenceException)
			{ }

			return result;
		}

		/// <summary>
		/// Returns the current trend, geo and sleep time information for the authenticating user. 
		/// </summary>
		/// <returns>TwitterAccountSettings object</returns>
		public TwitterAccountSettings GetAccountSettings()
		{
			var response = SendRequest(RequestMethod.Get, "https://api.twitter.com/1/account/settings.xml", true);
			var result = new TwitterAccountSettings(false, false, null, null);
			var doc = XDocument.Parse(response);
			var root = doc.Element("hash");
			try
			{
				if (root != null)
				{
					DateTime? start = null;
// ReSharper disable PossibleNullReferenceException
					var startTimeString = root.Element("start_time").Value;
					if (!string.IsNullOrEmpty(startTimeString))
						start = ParseTiwtterTime(startTimeString);

					DateTime? end = null;
					var endTimeString = root.Element("end_time").Value;
					if (!string.IsNullOrEmpty(endTimeString))
						end = ParseTiwtterTime(endTimeString);
					result = new TwitterAccountSettings(bool.Parse(root.Element("geo_enabled").Value),
														bool.Parse(root.Element("sleep_enabled").Value), start, end);
// ReSharper restore PossibleNullReferenceException
				}
			}
			catch (NullReferenceException)
			{ }

			return result;
		}

		#endregion

		private static string ColorToHexString(Color c)
		{
			return string.Format("{0}{1}{2}", c.R.ToString("X2"), c.G.ToString("X2"), c.B.ToString("X2")).ToLower();
		}
	}
}
